package main

import (
	"encoding/json"
	"fmt"
	"log"
	"math/big"

	"strconv"

	"github.com/hyperledger/fabric-contract-api-go/contractapi"
)

// SmartContract provides functions for transferring tokens between accounts
type SmartContract struct {
	contractapi.Contract
}

type BasicResponse struct {
	Code string      `json:"code"`
	Msg  string      `json:"msg"`
	Data interface{} `json:"data"`
}

const (
	RECODE_OK                  = "0"
	RECODE_UNKNOWERR           = "999"
	RECODE_USER_ALREADY_EXISTS = "100"
	RECODE_USER_NOT_EXISTS     = "101"
	RECODE_PASSWORD_ERR        = "102"
	RECODE_BALANCE_NOT_ENOUGH  = "103"
	RECODE_TASK_NOT_EXISTS     = "104"
	RECODE_TASK_CANNOT_TAKE    = "105"
	RECODE_TASK_CANNOT_COMMIT  = "106"
	RECODE_TASK_CANNOT_CONFRIM = "107"
	RECODE_CHAINCODE_PUT_ERR   = "108"
	RECODE_CHAINCODE_GET_ERR   = "109"
	RECODE_CHAINCODE_DEL_ERR   = "110"
	RECODE_JSON_ERR            = "111"
	RECODE_AUTH_ERR            = "112"
	RECODE_INVOKE_ERR          = "113"
)

const (
	STATUS_PUBLISH = iota
	STATUS_TAKE
	STATUS_COMMIT
	STATUS_CONFRIM
)

type FabTask struct {
	ID       int64  `json:"task_id"`
	Name     string `json:"name"`
	Issuer   string `json:"issuer"`
	Holder   string `json:"holder"`
	Status   int8   `json:"status"`
	Bonus    int    `json:"bonus"`
	TaskTime int64  `json:"task_time"`
	Comment  string `json:"comment"`
}

var current_value int

const taskSequence = "task_sequence"

/*
	[1 - 100) => task00000000
	101 - 200 => task00000001
*/
const basicTaskKeyPrefix = "zone%08d"

func getTaskKey(id int64) (string, error) {
	if id < 0 {
		return "", fmt.Errorf("id(%d) is invalid", id)
	}
	basic := id / 100
	return fmt.Sprintf(basicTaskKeyPrefix, basic), nil
}

// 一个自增序列
func newID(ctx contractapi.TransactionContextInterface) (int64, error) {

	ctx.GetStub().GetTransient()
	current_value, err := ctx.GetStub().GetState(taskSequence)
	if err != nil {
		return -1, err
	}
	val := big.NewInt(0).SetBytes(current_value).Int64()
	val++

	ctx.GetStub().PutState(taskSequence, big.NewInt(val).Bytes())

	return val, nil
}

// 发布任务
func (s *SmartContract) NewTask(
	ctx contractapi.TransactionContextInterface,
	issuer string,
	taskname string,
	bonus int) *BasicResponse {

	resp := BasicResponse{RECODE_OK, "OK", nil}
	clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
	if err != nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = fmt.Errorf("failed to get MSPID: %v", err).Error()
		return &resp
	}
	if clientMSPID != "Org1MSP" {
		resp.Code = RECODE_AUTH_ERR
		resp.Msg = fmt.Errorf("client is not authorized to mint new task").Error()
		return &resp
	}
	taskid, err := newID(ctx)
	if err != nil {
		resp.Msg = err.Error()
		return &resp
	}
	//构造一个新的task
	tt, _ := ctx.GetStub().GetTxTimestamp()
	task := FabTask{
		ID:       taskid,
		Name:     taskname,
		Issuer:   issuer,
		Bonus:    bonus,
		Status:   STATUS_PUBLISH,
		TaskTime: tt.GetSeconds(),
	}

	// 检查用户余额
	response := ctx.GetStub().InvokeChaincode("tokens", [][]byte{[]byte("BalanceOf"), []byte(issuer)}, "ykchannel")
	resp.Data = response
	if response.Status != 200 {
		resp.Code = RECODE_INVOKE_ERR
		resp.Msg = fmt.Errorf("failed to InvokeChaincode token's BalanceOf").Error()
		return &resp
	}

	balance, err := strconv.Atoi(string(response.Payload))
	if err != nil {
		resp.Code = RECODE_UNKNOWERR
		resp.Msg = err.Error()
		return &resp
	}
	if bonus <= 0 || balance < bonus {
		resp.Code = RECODE_BALANCE_NOT_ENOUGH
		resp.Msg = fmt.Errorf("user' balance not enough").Error()
		return &resp
	}

	// 获取key值
	taskkey, err := getTaskKey(taskid)
	if err != nil {
		resp.Code = RECODE_UNKNOWERR
		resp.Msg = fmt.Errorf("getTaskKey").Error()
		return &resp
	}

	data, err := ctx.GetStub().GetState(taskkey)
	if err != nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = err.Error()
		return &resp
	}
	var tasks []FabTask
	if data != nil {

		err = json.Unmarshal(data, &tasks)
		if err != nil {
			resp.Code = RECODE_JSON_ERR
			resp.Msg = err.Error()
			return &resp
		}
	}

	tasks = append(tasks, task)

	taskJson, err := json.Marshal(tasks)
	if err != nil {
		resp.Code = RECODE_JSON_ERR
		resp.Msg = err.Error()
		return &resp
	}

	err = ctx.GetStub().PutState(taskkey, taskJson)
	if err != nil {
		resp.Code = RECODE_CHAINCODE_PUT_ERR
		resp.Msg = err.Error()
		return &resp
	}
	resp.Data = taskkey

	return &resp
}

// 接受任务
func (s *SmartContract) TakeTask(
	ctx contractapi.TransactionContextInterface,
	id int64,
	operator string) *BasicResponse {

	resp := BasicResponse{RECODE_OK, "OK", nil}
	// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
	clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
	if err != nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = fmt.Errorf("failed to get MSPID: %v", err).Error()
		return &resp
	}
	if clientMSPID != "Org1MSP" {
		resp.Code = RECODE_AUTH_ERR
		resp.Msg = fmt.Errorf("client is not authorized to take task").Error()
		return &resp
	}

	taskkey, err := getTaskKey(id)
	if err != nil {
		resp.Code = RECODE_UNKNOWERR
		resp.Msg = err.Error()
		return &resp
	}

	currentBytes, err := ctx.GetStub().GetState(taskkey)
	if err != nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = err.Error()
		return &resp
	}

	if currentBytes == nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = fmt.Errorf("the id's state does not exists ：%v", taskkey).Error()
		return &resp
	}
	var tasks []FabTask
	err = json.Unmarshal(currentBytes, &tasks)
	if err != nil {
		resp.Code = RECODE_JSON_ERR
		resp.Msg = err.Error()
		return &resp
	}

	id = id % 100
	if int64(len(tasks)) < id {
		resp.Code = RECODE_TASK_NOT_EXISTS
		resp.Msg = fmt.Errorf("the %v's task does't exists", id).Error()
		return &resp
	}
	task := tasks[id-1]
	if task.Bonus <= 0 {
		resp.Code = RECODE_TASK_NOT_EXISTS
		resp.Msg = fmt.Errorf("the %v's task does't exists", task.ID).Error()
		return &resp
	}

	if task.Status != 0 {
		resp.Code = RECODE_TASK_CANNOT_TAKE
		resp.Msg = fmt.Errorf("the %v's task is already be taked", task.ID).Error()
		return &resp
	}

	tasks[id-1].Holder = operator
	tasks[id-1].Status = STATUS_TAKE

	taskJson, err := json.Marshal(&tasks)
	if err != nil {
		resp.Code = RECODE_JSON_ERR
		resp.Msg = err.Error()
		return &resp
	}

	err = ctx.GetStub().PutState(taskkey, taskJson)
	if err != nil {
		resp.Code = RECODE_CHAINCODE_PUT_ERR
		resp.Msg = err.Error()
		return &resp
	}

	return &resp
}

func (s *SmartContract) Reset(
	ctx contractapi.TransactionContextInterface,
	id int64, ccid int64,
) *BasicResponse {
	resp := BasicResponse{RECODE_OK, "OK", nil}
	// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
	clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
	if err != nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = fmt.Errorf("failed to get MSPID: %v", err).Error()
		return &resp
	}
	if clientMSPID != "Org1MSP" {
		resp.Code = RECODE_AUTH_ERR
		resp.Msg = fmt.Errorf("client is not authorized to take task").Error()
		return &resp
	}

	taskkey, err := getTaskKey(id)
	if err != nil {
		resp.Code = RECODE_UNKNOWERR
		resp.Msg = fmt.Errorf("getTaskKey").Error()
		return &resp
	}

	currentBytes, err := ctx.GetStub().GetState(taskkey)
	if err != nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = err.Error()
		return &resp
	}

	if currentBytes == nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = fmt.Errorf("the id's state does not exists ：%v", taskkey).Error()
		return &resp
	}
	var tasks []FabTask
	err = json.Unmarshal(currentBytes, &tasks)
	if err != nil {
		resp.Code = RECODE_JSON_ERR
		resp.Msg = err.Error()
		return &resp
	}

	id = id % 100
	if int64(len(tasks)) < id {
		resp.Code = RECODE_TASK_NOT_EXISTS
		resp.Msg = fmt.Errorf("the %v's task does't exists", id).Error()
		return &resp
	}
	//task := tasks[id-1]
	tasks[id-1].TaskTime = 1000
	tasks[id-1].ID = ccid

	taskJson, err := json.Marshal(&tasks)
	if err != nil {
		resp.Code = RECODE_JSON_ERR
		resp.Msg = err.Error()
		return &resp
	}

	err = ctx.GetStub().PutState(taskkey, taskJson)
	if err != nil {
		resp.Code = RECODE_CHAINCODE_PUT_ERR
		resp.Msg = err.Error()
		return &resp
	}
	resp.Data = tasks[id-1]

	return &resp
}

// 提交任务
func (s *SmartContract) CommitTask(
	ctx contractapi.TransactionContextInterface,
	id int64,
	operator string) *BasicResponse {

	resp := BasicResponse{RECODE_OK, "OK", nil}
	// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
	clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
	if err != nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = fmt.Errorf("failed to get MSPID: %v", err).Error()
		return &resp
	}
	if clientMSPID != "Org1MSP" {
		resp.Code = RECODE_AUTH_ERR
		resp.Msg = fmt.Errorf("client is not authorized to take task").Error()
		return &resp
	}

	taskkey, err := getTaskKey(id)
	if err != nil {
		resp.Code = RECODE_UNKNOWERR
		resp.Msg = fmt.Errorf("getTaskKey").Error()
		return &resp
	}

	currentBytes, err := ctx.GetStub().GetState(taskkey)
	if err != nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = err.Error()
		return &resp
	}

	if currentBytes == nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = fmt.Errorf("the id's state does not exists ：%v", taskkey).Error()
		return &resp
	}
	var tasks []FabTask
	err = json.Unmarshal(currentBytes, &tasks)
	if err != nil {
		resp.Code = RECODE_JSON_ERR
		resp.Msg = err.Error()
		return &resp
	}

	id = id % 100
	if int64(len(tasks)) < id {
		resp.Code = RECODE_TASK_NOT_EXISTS
		resp.Msg = fmt.Errorf("the %v's task does't exists", id).Error()
		return &resp
	}
	task := tasks[id-1]
	if task.Bonus <= 0 {
		resp.Code = RECODE_TASK_NOT_EXISTS
		resp.Msg = fmt.Errorf("the %v's task does't exists", task.ID).Error()
		return &resp
	}

	if task.Status != STATUS_TAKE {
		resp.Code = RECODE_TASK_CANNOT_COMMIT
		resp.Msg = fmt.Errorf("the %v's task can not be commited", task.ID).Error()
		return &resp
	}

	if task.Holder != operator {
		resp.Code = RECODE_TASK_CANNOT_COMMIT
		resp.Msg = fmt.Errorf("only the task's holder(%v:%v) can commit", task.ID, task.Holder, operator).Error()
		return &resp
	}
	tasks[id-1].Status = STATUS_COMMIT

	taskJson, err := json.Marshal(tasks)
	if err != nil {
		resp.Code = RECODE_JSON_ERR
		resp.Msg = err.Error()
		return &resp
	}

	err = ctx.GetStub().PutState(taskkey, taskJson)
	if err != nil {
		resp.Code = RECODE_CHAINCODE_PUT_ERR
		resp.Msg = err.Error()
		return &resp
	}

	log.Printf("task(%d) already be commited by %s", task.ID, task.Holder)

	return &resp
}

// 确认任务
func (s *SmartContract) ConfirmTask(
	ctx contractapi.TransactionContextInterface,
	id int64,
	operator string,
	status int8,
	comment string) *BasicResponse {

	resp := BasicResponse{RECODE_OK, "OK", nil}
	// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
	clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
	if err != nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = fmt.Errorf("failed to get MSPID: %v", err).Error()
		return &resp
	}
	if clientMSPID != "Org1MSP" {
		resp.Code = RECODE_AUTH_ERR
		resp.Msg = fmt.Errorf("client is not authorized to take task").Error()
		return &resp
	}

	taskkey, err := getTaskKey(id)
	if err != nil {
		resp.Code = RECODE_UNKNOWERR
		resp.Msg = fmt.Errorf("getTaskKey").Error()
		return &resp
	}

	currentBytes, err := ctx.GetStub().GetState(taskkey)
	if err != nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = err.Error()
		return &resp
	}

	if currentBytes == nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = fmt.Errorf("the id's state does not exists ：%v", taskkey).Error()
		return &resp
	}
	var tasks []FabTask
	err = json.Unmarshal(currentBytes, &tasks)
	if err != nil {
		resp.Code = RECODE_JSON_ERR
		resp.Msg = err.Error()
		return &resp
	}

	id = id % 100

	if int64(len(tasks)) < id {
		resp.Code = RECODE_TASK_NOT_EXISTS
		resp.Msg = fmt.Errorf("the %v's task does't exists", id).Error()
		return &resp
	}
	task := tasks[id-1]
	if task.Bonus <= 0 {
		resp.Code = RECODE_TASK_NOT_EXISTS
		resp.Msg = fmt.Errorf("the %v's task does't exists", task.ID).Error()
		return &resp
	}

	if task.Status != STATUS_COMMIT {
		resp.Code = RECODE_TASK_CANNOT_COMMIT
		resp.Msg = fmt.Errorf("the %v's task can not be commited", task.ID).Error()
		return &resp
	}

	if task.Issuer != operator {
		resp.Code = RECODE_TASK_CANNOT_CONFRIM
		resp.Msg = fmt.Errorf("only the task's issuer(%v:%v) can confirm", task.Issuer, operator).Error()
		return &resp
	}
	tasks[id-1].Status = status
	tasks[id-1].Comment = comment

	taskJson, err := json.Marshal(tasks)
	if err != nil {
		resp.Code = RECODE_JSON_ERR
		resp.Msg = err.Error()
		return &resp
	}

	err = ctx.GetStub().PutState(taskkey, taskJson)
	if err != nil {
		resp.Code = RECODE_CHAINCODE_PUT_ERR
		resp.Msg = err.Error()
		return &resp
	}

	// 发放奖励
	if status == STATUS_CONFRIM {
		bonus := fmt.Sprintf("%d", task.Bonus)
		response := ctx.GetStub().InvokeChaincode("tokens", [][]byte{[]byte("Transfer"), []byte(task.Issuer), []byte(task.Holder), []byte(bonus)}, "ykchannel")
		resp.Data = response
		if response.Status != 200 {
			resp.Code = RECODE_INVOKE_ERR
			resp.Msg = fmt.Errorf("failed to InvokeChaincode token's Transfer").Error()
			return &resp
		}
	}

	log.Printf("task(%d) already be confirmed by %s", task.ID, task.Issuer)

	return &resp
}

// ReadTask returns the task stored in the world state with given id.
func (s *SmartContract) ReadTask(ctx contractapi.TransactionContextInterface, id int64) *BasicResponse {

	resp := BasicResponse{RECODE_OK, "OK", nil}
	taskkey, err := getTaskKey(id)
	if err != nil {
		resp.Code = RECODE_UNKNOWERR
		resp.Msg = fmt.Errorf("getTaskKey").Error()
		return &resp
	}

	currentBytes, err := ctx.GetStub().GetState(taskkey)
	if err != nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = err.Error()
		return &resp
	}

	if currentBytes == nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = fmt.Errorf("the id's state does not exists ：%v", taskkey).Error()
		return &resp
	}
	var tasks []FabTask
	err = json.Unmarshal(currentBytes, &tasks)
	if err != nil {
		resp.Code = RECODE_JSON_ERR
		resp.Msg = err.Error()
		return &resp
	}

	id = id % 100
	if int64(len(tasks)) < id {
		resp.Code = RECODE_TASK_NOT_EXISTS
		resp.Msg = fmt.Errorf("the %v's task does't exists", id).Error()
		resp.Data = tasks
		return &resp
	}
	task := tasks[id-1]
	// if task.TaskTime <= 0 {
	// 	resp.Code = RECODE_TASK_NOT_EXISTS
	// 	resp.Msg = fmt.Errorf("the %v's task does't exists", task.ID).Error()
	// 	resp.Data = tasks
	// 	return &resp
	// }
	resp.Data = task

	return &resp
}

// GetAllTask returns all tasks found in world state
func (s *SmartContract) GetAllTask(ctx contractapi.TransactionContextInterface, page int) *BasicResponse {
	// range query with empty string for startKey and endKey does an
	// open-ended query of all tasks in the chaincode namespace.
	resp := BasicResponse{RECODE_OK, "OK", nil}

	/*
		page = 1   key = 0
		page = 2   key = 0
		page = 3   key = 0
		...
		page = 9   key = 0
		page = 10  key = 1

	*/
	if page <= 0 {
		page = 1
	}

	id := int64(page / 10)

	taskkey, err := getTaskKey(id)
	if err != nil {
		resp.Code = RECODE_UNKNOWERR
		resp.Msg = err.Error()
		return &resp
	}

	currentBytes, err := ctx.GetStub().GetState(taskkey)
	if err != nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = err.Error()
		return &resp
	}

	if currentBytes == nil {
		resp.Code = RECODE_CHAINCODE_GET_ERR
		resp.Msg = fmt.Errorf("the id's state does not exists ：%v", taskkey).Error()
		return &resp
	}
	var tasks []FabTask
	err = json.Unmarshal(currentBytes, &tasks)
	if err != nil {
		resp.Code = RECODE_JSON_ERR
		resp.Msg = err.Error()
		return &resp
	}
	resp.Data = tasks

	return &resp
}

func main() {
	taskChaincode, err := contractapi.NewChaincode(&SmartContract{})
	if err != nil {
		log.Panicf("Error creating task chaincode: %v", err)
	}

	if err := taskChaincode.Start(); err != nil {
		log.Panicf("Error starting task chaincode: %v", err)
	}
}
